package com.weihua.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.weihua.bo.BaseBo;
import com.weihua.po.BasePo;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public abstract class BaseServiceImpl<B extends BaseBo, P extends BasePo, M extends BaseMapper<P>> implements BaseService<B> {
    protected Log log = LogFactory.getLog(this.getClass());
    /**
     * 获取持久化对象
     *
     * @return 持久化对象
     */
    protected abstract M getMapper();

    private Class<P> getPoClass() {
        return this.getGenericClass(1);
    }

    private Class<B> getBoClass() {
        return this.getGenericClass(0);
    }

    private Class<M> getMapperClass(){
        return this.getGenericClass(2);
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> getGenericClass(int index) {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), index);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(B bo) {
        P po = this.transBo2Po(bo);
        int count = getMapper().insert(po);
        Class<P> pClass = this.getPoClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(pClass);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notNull(keyProperty, "error: can not execute. because can not find column for id from entity!");
        Object idVal = ReflectionKit.getFieldValue(po, keyProperty);
        if (!StringUtils.checkValNull(idVal) && !Objects.isNull(getById((Serializable) idVal))) {
            Class<B> bClass = this.getBoClass();
            Field[] fields = bClass.getDeclaredFields();
            Field f =
                    Arrays.stream(fields).filter(field -> field.getName().equals(keyProperty)).findFirst().get();
            try {
                f.setAccessible(true);
                f.set(bo, idVal);
            } catch (IllegalAccessException e) {
                Assert.isTrue(e == null, "error: can not execute. because can not find attribute for id from entity!");
            }
        }
        return SqlHelper.retBool(count);
    }

    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.getPoClass(), log, list, batchSize, consumer);
    }

    protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return this.executeBatch(list, 1000, consumer);
    }

    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(this.getMapperClass(), sqlMethod);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(Collection<B> boList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(boList, batchSize, (sqlSession, bo) -> {
            sqlSession.insert(sqlStatement, this.transBo2Po(bo));
        });
    }

    @Override
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public boolean saveOrUpdateBatch(Collection<B> boList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(this.getPoClass());
        com.baomidou.mybatisplus.core.toolkit.Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!", new Object[0]);
        String keyProperty = tableInfo.getKeyProperty();
        com.baomidou.mybatisplus.core.toolkit.Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!", new Object[0]);
        return SqlHelper.saveOrUpdateBatch(this.getPoClass(), this.getMapperClass(), this.log, boList, batchSize, (sqlSession, bo) -> {
            Object idVal = ReflectionKit.getFieldValue(this.transBo2Po(bo), keyProperty);
            return StringUtils.checkValNull(idVal) || CollectionUtils.isEmpty(sqlSession.selectList(this.getSqlStatement(SqlMethod.SELECT_BY_ID), this.transBo2Po(bo)));
        }, (sqlSession, bo) -> {
            MapperMethod.ParamMap<P> param = new MapperMethod.ParamMap();
            param.put("et", this.transBo2Po(bo));
            sqlSession.update(this.getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        });
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeById(Serializable id) {
        return SqlHelper.retBool(getMapper().deleteById(id));
    }

    /**
     * 删除数据
     *
     * @param queryWrapper 条件
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    protected boolean remove(LambdaQueryWrapper<P> queryWrapper) {
        return SqlHelper.retBool(getMapper().delete(queryWrapper));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<? extends Serializable> idList) {
        return CollectionUtils.isEmpty(idList) ? false : SqlHelper.retBool(getMapper().deleteBatchIds(idList));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(B bo) {
        return SqlHelper.retBool(getMapper().updateById(this.transBo2Po(bo)));
    }

    /**
     * 更新数据， Set 部分可以放到 Wrapper
     *
     * @param updateWrapper 条件
     * @return 是否成功
     */
    protected boolean update(LambdaUpdateWrapper<P> updateWrapper) {
        return SqlHelper.retBool(getMapper().update(null, updateWrapper));
    }

    /**
     * 批量更新数据
     *
     * @param bo            实体对象, SET 部分
     * @param updateWrapper 更新条件 WHERE 部分
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    protected boolean update(B bo, LambdaUpdateWrapper<P> updateWrapper) {
        return SqlHelper.retBool(getMapper().update(this.transBo2Po(bo), updateWrapper));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchById(Collection<B> boList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return this.executeBatch(boList, batchSize, (sqlSession, bo) -> {
            MapperMethod.ParamMap<P> param = new MapperMethod.ParamMap();
            param.put("et", this.transBo2Po(bo));
            sqlSession.update(sqlStatement, param);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdate(B bo) {
        if (null == bo) {
            return false;
        } else {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.getPoClass());
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notNull(keyProperty, "error: can not execute. because can not find column for id from entity!");
            P po = this.transBo2Po(bo);
            Object idVal = ReflectionKit.getFieldValue(po, tableInfo.getKeyProperty());
            return !StringUtils.checkValNull(idVal) && !Objects.isNull(this.getById((Serializable)idVal)) ? this.updateById(bo) : this.save(bo);
        }
    }

    @Override
    public B getById(Serializable id) {
        return this.transPo2Bo(getMapper().selectById(id));
    }

    @Override
    public Collection<B> listByIds(Collection<? extends Serializable> idList) {
        List<P> poList = getMapper().selectBatchIds(idList);
        return poList.parallelStream().map(this::transPo2Bo).collect(Collectors.toList());
    }

    /**
     * 查询数据. 数据超过一条时，返回第一条数据
     *
     * @param queryWrapper 查询条件
     * @return 返回实体对象
     */
    protected B getOne(LambdaQueryWrapper<P> queryWrapper) {
        List<P> poList = getMapper().selectList(queryWrapper);
        if (CollectionUtils.isEmpty(poList)) {
            return null;
        }
        int size = poList.size();
        if (size > 1) {
            log.warn("execute Method There are {} results.");
        }
        return this.transPo2Bo(poList.get(0));
    }

    /**
     * 统计数量
     *
     * @param queryWrapper 查询条件
     * @return 数据数量
     */
    protected int count(LambdaQueryWrapper<P> queryWrapper) {
        return SqlHelper.retCount(getMapper().selectCount(queryWrapper));
    }

    /**
     * 查询数据
     *
     * @param queryWrapper 查询条件
     * @return 实体对象集合
     */
    protected List<B> list(LambdaQueryWrapper<P> queryWrapper) {
        List<P> poList = getMapper().selectList(queryWrapper);
        return poList.parallelStream().map(this::transPo2Bo).collect(Collectors.toList());
    }

    /**
     * 查询数据
     *
     * @param queryWrapper 查询条件
     * @param mapper       转换函数
     * @param <V>          值类型
     * @return 数据列表
     */
    protected <V> List<V> listObjs(LambdaQueryWrapper<P> queryWrapper, Function<P, ? extends V> mapper) {
        return getMapper().selectList(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
    }

    /**
     * 查询数据（单条数据）
     *
     * @param queryWrapper 查询条件
     * @param mapper       转换函数
     * @param <V>          值类型
     * @return 数据对象
     */
    protected <V> V getObj(LambdaQueryWrapper<P> queryWrapper, Function<P, V> mapper) {
        List<V> voList = this.listObjs(queryWrapper, mapper);
        if (CollectionUtils.isEmpty(voList)) {
            return null;
        }
        int size = voList.size();
        if (size > 1) {
            log.warn("execute Method There are {} results.");
        }
        return voList.get(0);
    }

    /**
     * 分页查询
     *
     * @param page         分页信息
     * @param queryWrapper 查询条件
     * @return 分页数据
     */
    protected IPage<B> page(IPage<P> page, LambdaQueryWrapper<P> queryWrapper) {
        IPage<P> poPage = this.getMapper().selectPage(page, queryWrapper);
        return poPage.convert(this::transPo2Bo);
    }

    /**
     * 链式查询 lambda 式
     *
     * @return 查询对象
     */
    protected LambdaQueryWrapper<P> lambdaQuery() {
        return new LambdaQueryWrapper<>();
    }

    /**
     * 链式更改 lambda 式
     *
     * @return 查询对象
     */
    protected LambdaUpdateWrapper<P> lambdaUpdate() {
        return new LambdaUpdateWrapper<>();
    }

    /**
     * 转换业务实体到持久化实体
     *
     * @param bo 业务实体
     * @return 持久化实体
     */
    protected abstract P transBo2Po(B bo);

    /**
     * 转换持久化实体到业务实体
     *
     * @param po 持久化实体
     * @return 业务实体
     */
    protected abstract B transPo2Bo(P po);


}
